package cn.leestudio.restlib.core;

import android.content.Context;
import android.os.Handler;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import cn.leestudio.restlib.RestCallback;
import cn.leestudio.restlib.annotation.Cache;
import cn.leestudio.restlib.annotation.Field;
import cn.leestudio.restlib.annotation.FileField;
import cn.leestudio.restlib.annotation.GET;
import cn.leestudio.restlib.annotation.Head;
import cn.leestudio.restlib.annotation.JsonEntity;
import cn.leestudio.restlib.annotation.Multipart;
import cn.leestudio.restlib.annotation.POST;
import cn.leestudio.restlib.annotation.Path;
import cn.leestudio.restlib.annotation.Query;
import cn.leestudio.restlib.cache.CacheBean;
import cn.leestudio.restlib.cache.CacheManager;
import cn.leestudio.restlib.cache.CachePolicy;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * 描 述: 接口实际请求持有抽象类
 *
 * @author: lihui
 * @date: 2017-11-10 17:07
 */

public abstract class AbsCall {

    private static final String TAG = AbsCall.class.getSimpleName();
    private HttpMethod mHttpMethod;
    private Method mMethod = null;
    private Object[] mArgs;
    private Annotation[] methodAnnotations;
    private Annotation[][] parameterAnnotationsArray;

    private StringBuffer mUrlBuf = new StringBuffer();
    private StringBuffer mCommonQueryBuf;
    private String mUrl;
    private Headers.Builder mHeaders;
    /**
     * 表单键值对提交
     */
    private FormBody.Builder mFormBody;
    /**
     * 上传提交
     */
    private MultipartBody.Builder mMultipartBody;
    /**
     * JSON 提交
     */
    private RequestBody mRequestBody;
    private Call mCall;
    private boolean isCancle = false;
    private boolean isMultipart = false;
    private boolean isJson = false;
    private RestCallback mRestCallback;
    protected String service = "";
    //缓存相关
    private CacheManager mCacheManager;
    private String cacheKey = "";
    private int cacheTime = -1;
    private CachePolicy mCachePolicy = CachePolicy.OnlineFirst;
    private Handler mHandler;


    /**
     * 添加查询公共参数
     */
    protected abstract void addCommonQuery();


    /**
     * 添加头部公共参数
     */
    protected abstract void addCommonHead();


    /**
     * 设置基础地址
     */
    protected abstract String getBaseUrl();

    /**
     * 请求结果处理
     * 默认调用doResult方法处理返回结果
     * 如果需要对结果进行预处理，可重写本方法逻辑。
     * 例如需要预处理{"statu":"500","result":{xxxxx}}格式，只提取result内容返回给doResult进行处理等
     *
     * @param result response.body内容
     * @return
     */
    protected void respFilter(String result) {
        doResult(result);
    }

    public void init(Context context, Method method, Object... args) {
        mCacheManager = new CacheManager(context);
        mHandler = new Handler(context.getMainLooper());
        this.mMethod = method;
        this.mArgs = args;
        this.methodAnnotations = method.getAnnotations();
        this.parameterAnnotationsArray = method.getParameterAnnotations();
        //获取回调监听器
        getCallback();
        //解析方法注解
        parseMethodAnnotation();
        //解析参数注解
        parseParameterAnnotation();
        //拼接完整的url地址，包括公共参数
        buildFullUrl();
        //设置公共head参数
        addCommonHead();
    }

    /**
     * 拼接完整的url地址
     */
    private void buildFullUrl() {
        mUrlBuf.append(getBaseUrl()).append(mUrl);
        addCommonQuery();
        if (mCommonQueryBuf.length() > 1) {
            if (mUrlBuf.toString().contains("?")) {
                mUrlBuf.append(mCommonQueryBuf);
            } else {
                mUrlBuf.append("?").append(mCommonQueryBuf.substring(1));
            }
        }
    }


    /**
     * 解析出需要回调的监听类，并调用onstart接口
     */
    private void getCallback() {
        for (Object obj : mArgs) {
            if (obj instanceof RestCallback) {
                mRestCallback = (RestCallback) obj;
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mRestCallback.start();
                    }
                });
                return;
            }
        }
    }

    /**
     * 解析参数注解
     */
    private void parseParameterAnnotation() {
        mUrlBuf = new StringBuffer();
        mFormBody = new FormBody.Builder();
        mMultipartBody = new MultipartBody.Builder();
        mMultipartBody.setType(MediaType.parse("multipart/form-data"));
        mHeaders = new Headers.Builder();
        for (int i = 0; i < parameterAnnotationsArray.length; i++) {
            Annotation[] paramAnnotations = parameterAnnotationsArray[i];
            for (int m = 0; m < paramAnnotations.length; m++) {
                if (Head.class.equals(paramAnnotations[m].annotationType())) {
                    Head head = (Head) paramAnnotations[m];
                    try {
                        mHeaders.set(head.value(), new String(((String) mArgs[i]).getBytes("ISO-8859-1"), "utf-8"));
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
                if (Field.class.equals(paramAnnotations[m].annotationType())) {
                    Field field = (Field) paramAnnotations[m];
                    if (isMultipart) {
                        mMultipartBody.addFormDataPart(field.value(), (String) mArgs[i]);
                    } else {
                        mFormBody.add(field.value(), (String) mArgs[i]);
                    }
                }
                if (FileField.class.equals(paramAnnotations[m].annotationType())) {
                    FileField fileField = (FileField) paramAnnotations[m];
                    if (isMultipart) {
                        File file = new File((String) mArgs[i]);
                        mMultipartBody.addFormDataPart(fileField.value(), file.getName(), RequestBody.create(MultipartBody.FORM, file));
                    }
                }
                if (JsonEntity.class.equals(paramAnnotations[m].annotationType())) {
                    JsonEntity json = (JsonEntity) paramAnnotations[m];
                    mRequestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json.value());
                }
                if (Path.class.equals(paramAnnotations[m].annotationType())) {
                    Path path = (Path) paramAnnotations[m];
                    mUrl = mUrl.replace(path.value(), (String) mArgs[i]);
                    service = service.replace(path.value(), (String) mArgs[i]);
                }
                if (Query.class.equals(paramAnnotations[m].annotationType())) {
                    Query query = (Query) paramAnnotations[m];
                    if (mUrl.contains("?")) {
                        mUrl = mUrl + mUrlBuf + "&" + query.value() + "=" + mArgs[i];
                    } else {
                        mUrl = mUrl + mUrlBuf + "?" + query.value() + "=" + mArgs[i];
                    }
                }
            }
        }
        cacheKey = mUrl;

    }

    /**
     * 解析方法注解
     */
    private void parseMethodAnnotation() {
        for (int i = 0; i < methodAnnotations.length; i++) {
            if (methodAnnotations[i].annotationType().equals(POST.class)) {
                mHttpMethod = HttpMethod.POST;
                POST post = (POST) methodAnnotations[i];
                mUrl = post.value();
                service = post.value();
            }
            if (methodAnnotations[i].annotationType().equals(GET.class)) {
                mHttpMethod = HttpMethod.GET;
                GET get = (GET) methodAnnotations[i];
                mUrl = get.value();
                service = get.value();
            }
            if (Multipart.class.equals(methodAnnotations[i].annotationType())) {
                isMultipart = true;
            }
            if (Cache.class.equals(methodAnnotations[i].annotationType())) {
                Cache cache = (Cache) methodAnnotations[i];
                cacheTime = cache.time();
                mCachePolicy = CachePolicy.valueOf(cache.policy());
            }

        }
    }

    /**
     * 执行请求操作
     */
    public void exec() {
        OkHttpClient client = new OkHttpClient.Builder().build();
        Request.Builder builer = new Request.Builder();
        //设置地址，此处地址已经拼接好
        builer.url(mUrlBuf.toString());
        //设置头部
        builer.headers(mHeaders.build());
        //如果需要，设置Body
        if (mHttpMethod.equals(HttpMethod.POST)) {
            if (isMultipart && isJson) {
                throw new RuntimeException("Multipart和JsonEntity注解不可同时存在");
            }
            //判断是否是文件上传，如果是，使用MultipartBody
            if (isMultipart) {
                builer.post(mMultipartBody.build());
            } else if (isJson) {
                builer.post(mRequestBody);
            } else {
                builer.post(mFormBody.build());
            }
        }

        //缓存处理,只有get请求才有缓存
        if (HttpMethod.GET.equals(mHttpMethod)) {
            if (mCachePolicy.equals(CachePolicy.Both)
                    || mCachePolicy.equals(CachePolicy.OutlineFirst)) {
                CacheBean cb = mCacheManager.getCache(cacheKey);
                //如果当前没有缓存内容，不做任何处理，继续请求
                if (cb.getContent() != null && cb.getContent().length() > 0) {
                    //如果是缓存优先策略，判断缓存是否过期，没有过期则回调，并停止后续流程
                    long t = (System.currentTimeMillis() - cb.getTime()) / 1000 / 60;
                    if (mCachePolicy.equals(CachePolicy.OutlineFirst)
                            && t < cacheTime) {
                        parseRespBody(cb.getContent(), true);
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mRestCallback.stop();
                            }
                        });

                        return;
                    } else {
                        //如果是预览策略，不管是否过期，都回调，并继续后续流程
                        parseRespBody(cb.getContent(), true);
                    }
                }
            }

        }
        Request request = builer.build();
        if (!isCancle) {
            mCall = client.newCall(request);
            okhttp3.Callback callback = new okhttp3.Callback() {
                @Override
                public void onFailure(Call call, final IOException e) {
                    if (!isCancle) {
                        e.printStackTrace();
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
//                                mRestCallback.error(RestStatus.HTTP_UNDEFINE.value() + "", (e.getMessage() == null) ? "" : e.getMessage());
                                mRestCallback.error(RestStatus.HTTP_UNDEFINE.value() + "", RestStatus.HTTP_UNDEFINE.desc());
                                mRestCallback.stop();
                            }
                        });

                        String resultCache = mCacheManager.getCache(cacheKey).getContent();

                        if (resultCache != null && resultCache.length() > 0) {
                            respFilter(resultCache);
                        }
                    }
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (!isCancle) {
                        String body = response.body().string();
                        if (mRestCallback == null) throw new RuntimeException("没有定义回调Callback");
                        if (response.code() == RestStatus.HTTP_OK.value()) {
                            if (HttpMethod.GET.equals(mHttpMethod)) {
                                mCacheManager.writeCache(cacheKey, new CacheBean(System.currentTimeMillis(), body));
                            }
                            respFilter(body);
                        } else {
                            doError(response.code() + "", response.message());
                        }

                    }

                }
            };
            mCall.enqueue(callback);
        }

    }

    protected void doError(final String code, final String msg) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mRestCallback.error(code, msg);
                mRestCallback.stop();
            }
        });

    }

    protected void doErrWithJson(final String code, final String result) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mRestCallback.errWithJson(code, result);
            }
        });
    }

    protected void doResult(final String result) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                parseRespBody(result, false);
                mRestCallback.stop();
            }
        });

    }

    /**
     * 请求结果处理
     *
     * @param result
     * @param isCache
     */
    private void parseRespBody(final String result, final boolean isCache) {
        if (!isCancle) {
            Type returnType = getCallBackType(mMethod);
            if (returnType.equals(String.class)) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (isCache) {
                            mRestCallback.cache(result);
                        } else {
                            mRestCallback.success(result);
                        }
                    }
                });

            } else {
                try {
                    final Object resultObj = new Gson().fromJson(result, returnType);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (isCache) {
                                mRestCallback.cache(resultObj);
                            } else {
                                mRestCallback.success(resultObj);
                            }
                        }
                    });

                } catch (final JsonSyntaxException e) {
                    e.printStackTrace();
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
//                            mRestCallback.error(RestStatus.HTTP_JSON_ERROR.value() + "", (e.getMessage() == null) ? "" : e.getMessage());
                            mRestCallback.error(RestStatus.HTTP_JSON_ERROR.value() + "", RestStatus.HTTP_JSON_ERROR.desc());
                        }
                    });

                }

            }
        }

    }

    /**
     * 取消请求
     */
    public void cancle() {
        isCancle = true;
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mCall != null) {
                    mCall.cancel();
                }
                if (mRestCallback != null) {
                    mRestCallback.stop();
                }
            }
        });

    }

    /**
     * 提取回调监听类型
     *
     * @param method
     * @return
     */
    private Type getCallBackType(Method method) {
        if (!isCancle) {
            Type[] paramTypes = method.getGenericParameterTypes();
            for (Type paramType : paramTypes) {
                if (paramType instanceof ParameterizedType) {
                    if (((ParameterizedType) paramType).getRawType().equals(RestCallback.class)) {
                        Type[] typesto = ((ParameterizedType) paramType).getActualTypeArguments();
                        if (typesto.length > 1 || typesto.length == 0) {
                            throw new RuntimeException("Callback类中需要设置一个解析指向类");
                        } else {
                            Log.d(TAG, "参数泛型类型" + typesto[0]);
                            return typesto[0];
                        }
                    }
                }
            }
            throw new RuntimeException(method.getName() + "方法中没有定义Callback参数类");
        }
        return null;

    }

    /**
     * 添加公共查询参数
     *
     * @param key
     * @param value
     */
    protected void setQueryNameAndValue(String key, String value) {
        if (mCommonQueryBuf == null) {
            mCommonQueryBuf = new StringBuffer();
        }
        mCommonQueryBuf.append("&" + key + "=" + value);
    }

    protected void setHeadNameAndValue(String key, String value) {
        mHeaders.set(key, value);
    }


}
